/*
 * jabberd - Jabber Open Source Server
 * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
 *                    Ryan Eatmon, Robert Norris
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
 */

 /**
  * Antti Leppä / Foyt 2010
  *
  * Storage method for Jabberd2 that retrieves all information via
  * JSON requests.
  *
  * Following options need to be configured in sm.xml in order to use this method.
  *
  * <json>
  *   <host>www.myhost.com</host>
  *   <port>80</port>
  *   <getpath>/some/path/to/get.json</getpath>
  *   <setpath>/some/path/to/set.json</setpath>
  *   <encrypt>none/ssl</encrypt>
  *   <authentication>none/secret/basicauth/digestauth</authentication>
  *   <!-- secret needs to be defined if authentication is set to secret-->
  *   <secret>mysecret</secret>
  *   <!-- username and password fields need to be defined if authentication is set either to basicauth or digestauth -->
  *   <username>username</username>
  *   <password>password</password>
  * </json>
  *
  * Server should return JSON string in following format:
  *
  * {
  *   "sets": [												<-- 	1..n sets
  *     [											 		<--		sets contain 1..n fields
  *       {"type": "BOOLEAN/INTEGER/STRING/NAD/UNKNOWN"}, 	<--		field type
  *       {"key": "KEY"},									<--		field name
  *       {"value": "VALUE"}								<--		field value
  *     ]
  *   ],
  *   "statusCode": 0										<-- 	status code. return 0 if everything went ok
  * }
  *
  * This requires libjson0 to be installed.
  */

  /**
   * !!! This is code is still in highly experimental stage !!!
   */

#include "sm.h"
#include "json_request.h"

#include <json/json.h>

typedef struct drvdata_st {
  char *geturl;
  char *seturl;
  char *authentication;
  char *encrypt;
  char *secret;
  char *username;
  char *password;
} *drvdata_t;

static st_ret_t _st_json_send_handle_update(st_driver_t drv, const char *mode, const char *type, const char *owner, const char *filter, os_t os) {
  drvdata_t data = (drvdata_t) drv->private;
  char *params_template;
  os_object_t o;
  char *key, *cval = NULL;
  char *enc_cval;
  void *val;
  os_type_t ot;
  char *xml;
  int xlen;
  int params_len;
  char *params;
  struct json_response_t json_response;

  if(os_count(os) == 0)
	return st_SUCCESS;

  params_template = "mode=%s&type=%s&owner=%s&secret=%s&filter=%s";
  params_len = strlen(params_template) + strlen(mode) + strlen(type) + strlen(owner) + strlen(data->secret) + strlen(filter != NULL ? filter : "");
  params = malloc(params_len * sizeof(char));
  sprintf(params, params_template, mode, type, owner, data->secret, filter != NULL ? filter : "");

  if (os_iter_first(os)) {
	do {
	  o = os_iter_object(os);

	  if (os_object_iter_first(o)) {
		do {
		  os_object_iter_get(o, &key, &val, &ot);

		  switch(ot) {
		    case os_type_BOOLEAN:
			  cval = val ? strdup("1") : strdup("0");
		    break;
		    case os_type_INTEGER:
			  cval = (char *) malloc(sizeof(char) * 20);
			  sprintf(cval, "%d", (int) val);
		    break;
		    case os_type_STRING:
			  cval = (char *) malloc(sizeof(char) * ((strlen((char *) val) * 2) + 1));
			  strcpy(cval, val);
		    break;
		    case os_type_NAD:
			  nad_print((nad_t) val, 0, &xml, &xlen);
			  cval = malloc(sizeof(char) * (xlen + 4));
			  xml[xlen] = '\0';
			  sprintf(cval, "NAD%s", xml);
		    break;
		    case os_type_UNKNOWN:
		    break;
		  }

		  if (cval != NULL) {
			params_len += 1 + strlen(key) + 1 + strlen(cval);
			params = realloc(params, (params_len + 1) * sizeof(char));
			strcat(params, "&");
			strcat(params, key);
			strcat(params, "=");
			strcat(params, cval);
			params[params_len] = '\0';

			free(cval);
		  }
        } while(os_object_iter_next(o));
	  }
    } while(os_iter_next(os));
  }

  json_response = json_http_request(data->seturl, params);
  free(params);

  if (json_response.status != 0) {
 	log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: Get request failed with json error: %d", json_response.status);
	free_json_response(json_response);
    return st_FAILED;
  }

  free_json_response(json_response);
  return st_SUCCESS;
}

static st_ret_t _st_json_add_type(st_driver_t drv, const char *type) {
  return st_SUCCESS;
}

static st_ret_t _st_json_get(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os) {
  drvdata_t data = (drvdata_t) drv->private;

  char *params_template;
  char *params;
  int params_len;
  int sets_length;
  int ival;
  int i;
  int j;
  char *ftype;
  char *key;
  char *val;
  int fields_length;

  os_type_t ot;
  os_object_t o;

  struct json_response_t json_response;
  json_object *json_sets;
  json_object *json_fields;
  json_object *json_field;
  json_object *json_type;
  json_object *json_key;
  json_object *json_value;

  params_template = "type=%s&owner=%s&secret=%s&filter=%s";
  params_len = strlen(params_template) + strlen(type) + strlen(owner) + strlen(data->secret) + strlen(filter != NULL ? filter : "");
  params = malloc(params_len * sizeof(char));
  sprintf(params, params_template, type, owner, data->secret, filter != NULL ? filter : "");

  json_response = json_http_request(data->geturl, params);
  free(params);

  if (json_response.status != 0) {
	log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: Get request failed with json error: %d", json_response.status);
	free_json_response(json_response);
	return st_FAILED;
  }

  json_sets = json_object_object_get(json_response.json, "sets");

  if (json_sets == NULL) {
	free_json_response(json_response);
	return st_NOTFOUND;
  }

  sets_length = json_object_array_length(json_sets);

  if (sets_length == 0) {
    free_json_response(json_response);
    return st_NOTFOUND;
  }

  *os = os_new();

  for (i = 0; i < sets_length; i++) {
    o = os_object_new(*os);
    json_fields = json_object_array_get_idx(json_sets, i);
    fields_length = json_object_array_length(json_fields);
    for (j = 0; j < fields_length; j++) {
	  json_field = json_object_array_get_idx(json_fields, j);

	  json_type = json_object_object_get(json_field, "type");
	  json_key = json_object_object_get(json_field, "key");
	  json_value = json_object_object_get(json_field, "value");

	  ftype = (char *) json_object_get_string(json_type);
	  key = (char *) json_object_get_string(json_key);
	  val = (char *) json_object_get_string(json_value);

	  ot = os_type_UNKNOWN;

	  if (strcmp(ftype, "BOOLEAN") == 0)
		ot = os_type_BOOLEAN;
	  else if (strcmp(ftype, "INTEGER") == 0)
	    ot = os_type_INTEGER;
	  else if (strcmp(ftype, "STRING") == 0)
		ot = os_type_STRING;
	  else if (strcmp(ftype, "NAD") == 0)
	    ot = os_type_NAD;

	  switch(ot) {
		case os_type_BOOLEAN:
		  ival = (val[0] == '0') ? 0 : 1;
		  os_object_put(o, key, val, ot);
        break;
		case os_type_INTEGER:
		  ival = atoi(val);
		  os_object_put(o, key, val, ot);
	    break;
		case os_type_STRING:
		  os_object_put(o, key, val, ot);
		break;
		case os_type_NAD:
		case os_type_UNKNOWN:
		break;
	  }
	}
  }

  free_json_response(json_response);

  return st_SUCCESS;
}

static st_ret_t _st_json_put(st_driver_t drv, const char *type, const char *owner, os_t os) {
  return _st_json_send_handle_update(drv, "put", type, owner, NULL, os);
}

static st_ret_t _st_json_replace(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os) {
  return _st_json_send_handle_update(drv, "replace", type, owner, filter, os);
}

static st_ret_t _st_json_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) {
  drvdata_t data = (drvdata_t) drv->private;

  char *params_template;
  int params_len;
  char *params;
  struct json_response_t json_response;

  params_template = "mode=delete&type=%s&owner=%s&secret=%s&filter=%s";
  params_len = strlen(params_template) + strlen(type) + strlen(owner) + strlen(data->secret) + strlen(filter != NULL ? filter : "");
  params = malloc(params_len * sizeof(char));
  sprintf(params, params_template, type, owner, data->secret, filter != NULL ? filter : "");

  json_response = json_http_request(data->geturl, params);
  free(params);

  if (json_response.status != 0) {
	log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: Delete request failed with JSON error: %d", json_response.status);
	free_json_response(json_response);
	return st_FAILED;
  }

  free_json_response(json_response);

  return st_SUCCESS;
}

static void _st_json_free(st_driver_t drv) {
  drvdata_t data = (drvdata_t) drv->private;

  json_deinitialize();

  free(data);
}

DLLEXPORT st_ret_t st_init(st_driver_t drv) {
  char *host;
  char *port;
  char *getpath;
  char *setpath;
  char *authentication;
  char *encrypt;
  char *secret;
  char *username;
  char *password;

  log_write(drv->st->sm->log, LOG_NOTICE, "JSON Storage: initializing");

  host = config_get_one(drv->st->sm->config, "storage.json.host", 0);
  port = config_get_one(drv->st->sm->config, "storage.json.port", 0);
  getpath = config_get_one(drv->st->sm->config, "storage.json.getpath", 0);
  setpath = config_get_one(drv->st->sm->config, "storage.json.setpath", 0);
  authentication = config_get_one(drv->st->sm->config, "storage.json.authentication", 0);
  encrypt = config_get_one(drv->st->sm->config, "storage.json.encrypt", 0);
  secret = config_get_one(drv->st->sm->config, "storage.json.secret", 0);
  username = config_get_one(drv->st->sm->config, "storage.json.username", 0);
  password = config_get_one(drv->st->sm->config, "storage.json.password", 0);

  if (host == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no host specified in config file");
    return st_FAILED;
  }

  if (port == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no port specified in config file");
    return st_FAILED;
  }

  if (getpath == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no getpath specified in config file");
    return st_FAILED;
  }

  if (setpath == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no setpath specified in config file");
    return st_FAILED;
  }

  if (authentication  == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no authentication specified in config file");
    return st_FAILED;
  }

  if (encrypt == NULL) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no encrypt specified in config file");
    return st_FAILED;
  }

  if (strcmp(encrypt, "none") == 0) {
  } else if (strcmp(encrypt, "ssl") == 0) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: ssl encrytion is not implemented yet");
    return st_FAILED;
  } else {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: unknown encryption specified: %s", encrypt);
    return st_FAILED;
  }

  if (strcmp(authentication, "none") == 0) {
  } else if (strcmp(authentication, "secret") == 0) {
    if (secret == NULL) {
      log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no secret specified in config file.");
      return st_FAILED;
    }
  } else if (strcmp(authentication, "basicauth") == 0) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: authentication %s is not implemented yet", authentication);
    return st_FAILED;

    if (username == NULL) {
      log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no username specified in config file.");
      return st_FAILED;
    }

    if (password == NULL) {
	  log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no password specified in config file.");
	  return st_FAILED;
	}
  } else if (strcmp(authentication, "digestauth") == 0) {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: authentication %s is not implemented yet", authentication);
    return st_FAILED;

	if (username == NULL) {
	  log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no username specified in config file.");
	  return st_FAILED;
	}

	if (password == NULL) {
	  log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: no password specified in config file.");
	  return st_FAILED;
	}
  } else {
    log_write(drv->st->sm->log, LOG_ERR, "JSON Storage: unknown authentication mode (%s) specified in config file", authentication);
    return st_FAILED;
  }

  drvdata_t data = (drvdata_t) calloc(1, sizeof(struct drvdata_st));
  drv->private = (void *) data;

  data->geturl = get_url("http", host, port, getpath);
  data->seturl = get_url("http", host, port, setpath);
  data->authentication = authentication;
  data->encrypt = encrypt;
  data->secret = secret;
  data->username = username;
  data->password = password;

  drv->add_type = _st_json_add_type;
  drv->put = _st_json_put;
  drv->get = _st_json_get;
  drv->delete = _st_json_delete;
  drv->replace = _st_json_replace;
  drv->free = _st_json_free;

  json_initialize();

  log_write(drv->st->sm->log, LOG_NOTICE, "JSON Storage: initialized");

  return st_SUCCESS;
}
